package com.puzzlebazar.client.user;

import java.util.Iterator;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.puzzlebazar.client.main.view.ErrorMessages;
import com.puzzlebazar.client.util.BasicFilter;
import com.puzzlebazar.client.util.ConcurrentVector;
import com.puzzlebazar.client.util.Field;
import com.puzzlebazar.client.util.LabeledField;
import com.puzzlebazar.client.util.Releasable;
import com.puzzlebazar.client.util.StaticFieldImpl;
import com.puzzlebazar.client.util.ValidatedField;

public class SettingsGeneralView extends Composite implements Releasable {	
	
	public static final int LABEL_COLUMN_WIDTH = 100;
	public static final int FIELD_COLUMN_WIDTH = 210;
	public static final int APPLY_BUTTON_WIDTH = 170;
	public static final int CANCEL_BUTTON_WIDTH = 100;

	public static final int UNHANDLED_ERRORS_MASK = ~(0);	
	
	public static final String standardApplyText = "Apply modifications";
	public static final String registrationApplyText = "Register";
	
	private final Grid grid = new Grid();
	private final Field  emailField;
	private final LabeledField nicknameField;
	private final LabeledField firstnameField;
	private final LabeledField lastnameField;

	private final Button applyButton = new Button(standardApplyText);
	private final Button cancelButton = new Button("Cancel");

	private final MyChangeHandler changeHandler = new MyChangeHandler();
	
	private final ConcurrentVector<ProfileEditionCallback> callbacks = 
		new ConcurrentVector<ProfileEditionCallback>();

	private UserInfo currentUserInfo;
	private boolean modified = false;


	/**
	 * Creates a composite widget that prompts the user for its various informations
	 */
	public SettingsGeneralView() {

		grid.setWidth( "100%" );
		grid.setCellPadding( 7 );
		grid.setCellSpacing( 3 );
		grid.resize( 4, 3 );

		emailField = new StaticFieldImpl( 
				"Email:", "" );

		nicknameField = new ValidatedField( 
				"Nickname:", 
				UserInfo.MIN_NICKNAME_LENGTH,
				UserInfo.MAX_NICKNAME_LENGTH,
				new BasicFilter(new char[]{' ','-','.'}),
				ErrorMessages.ERROR_NICKNAME_EMPTY );

		firstnameField = new ValidatedField( 
				"First name:", 
				UserInfo.MIN_FIRSTNAME_LENGTH,
				UserInfo.MAX_FIRSTNAME_LENGTH,
				new BasicFilter(new char[]{' ','-'}),
				ErrorMessages.ERROR_FIRSTNAME_EMPTY );

		lastnameField = new ValidatedField( 
				"Last name:", 
				UserInfo.MIN_LASTNAME_LENGTH,
				UserInfo.MAX_LASTNAME_LENGTH,
				new BasicFilter(new char[]{' ','-'}),
				ErrorMessages.ERROR_LASTNAME_EMPTY );

		
		nicknameField.addChangeHandler( changeHandler );
		firstnameField.addChangeHandler( changeHandler ); 
		lastnameField.addChangeHandler( changeHandler ); 
		
		emailField.addToGrid( grid, 0 );
		nicknameField.addToGrid( grid, 1 );
		firstnameField.addToGrid( grid, 2 );
		lastnameField.addToGrid( grid, 3 );
		grid.getColumnFormatter().setWidth(0, LABEL_COLUMN_WIDTH + "px");
		grid.getColumnFormatter().setWidth(1, FIELD_COLUMN_WIDTH + "px");
		applyButton.setWidth( APPLY_BUTTON_WIDTH + "px" );		
		cancelButton.setWidth( CANCEL_BUTTON_WIDTH + "px" );
		
		applyButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				UserInfo userInfo = createUserInfo();
				if( userInfo == null ) return;
				executeApply( userInfo );
			}
		});	

		cancelButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				executeCancel();
			}
		});		
		
		final HorizontalPanel buttons = new HorizontalPanel();
		buttons.setWidth( APPLY_BUTTON_WIDTH + CANCEL_BUTTON_WIDTH + 10 + "px" );
		buttons.add(applyButton);
		buttons.add(cancelButton);
		
		final VerticalPanel mainPanel = new VerticalPanel();
		mainPanel.add( grid );
		mainPanel.add( buttons );


		mainPanel.setStylePrimaryName( "mainContent" );
		mainPanel.setWidth("100%");
		initWidget(mainPanel);		
		
	}	

	/**
	 * Clears all the states of the dialog box so that it can be reused
	 */
	public void resetState( UserInfo currentUserInfo ) {
		this.currentUserInfo = currentUserInfo;
		modified = false;
		
		emailField.setEntry( currentUserInfo.getEmail() );
		nicknameField.setEntry( currentUserInfo.getNickname() );
		firstnameField.setEntry( currentUserInfo.getFirstname() );
		lastnameField.setEntry( currentUserInfo.getLastname() );
		DeferredCommand.addCommand( new Command() {
			@Override
			public void execute() {
				nicknameField.setFocus( true );
			}	
		});

		checkApplyEnabled();	
	}

	/**
	 * Creates the user info based on the content of the text boxes
	 * 
	 * @return The user info, or null if some text boxes are invalid
	 */
	private UserInfo createUserInfo() {

		if( !checkApplyEnabled() ) return null;
		
		UserInfo userInfo = new UserInfo();
		userInfo.setEmail( currentUserInfo.getEmail() );
		userInfo.setNickname( nicknameField.getEntry() );
		userInfo.setFirstname( firstnameField.getEntry() );
		userInfo.setLastname( lastnameField.getEntry() );
		userInfo.setAdministrator( currentUserInfo.isAdministrator() );
		
		return userInfo;
	}

	/**
	 * Checks if the apply button should be enabled
	 * 
	 * @return True if the status of the fields is ok, false otherwise
	 */
	private boolean checkApplyEnabled() {

		boolean hasError = 
			nicknameField.hasError() || 
			firstnameField.hasError() ||
			lastnameField.hasError();
		
		boolean isSame = true;
		
		if( currentUserInfo != null ) {
			isSame = nicknameField.getEntry().equals( currentUserInfo.getNickname() ) &&
				firstnameField.getEntry().equals( currentUserInfo.getFirstname() ) &&
				lastnameField.getEntry().equals( currentUserInfo.getLastname() );
			if( isSame )
				executeModify( false );
		}
		
		applyButton.setEnabled( !hasError && !isSame );
		
		return !hasError && !isSame;
	}


	/**
	 * Adds a callback to be executed when the user logins
	 * 
	 * @param callback The callback to add
	 */
	public void addCallback( final ProfileEditionCallback callback ) {
		callbacks.add( callback );			
	}

	/**
	 * Remove a callback added with addCallback()
	 * 
	 * @param callback The callback to remove
	 */
	public void removeCallback( ProfileEditionCallback callback ) {
		callbacks.remove( callback );		
	}
	
	public interface ProfileEditionCallback {

		/**
		 * Called when modification status changes
		 *
		 * @param isDifferent True if the content of the fields differ from the original user information
		 */
		public void modified( boolean isDifferent );
		
		
		/**
		 * Called whenever the apply button is clicked
		 * 
		 * @param userInfo The information for this user
		 */
		public void apply( UserInfo userInfo );
		
		/**
		 * Called whenever the cancel button is clicked
		 */
		public void cancel();
	}	


	private class MyChangeHandler implements ChangeHandler {
		@Override
		public void onChange(ChangeEvent event) {
			executeModify( true );
			checkApplyEnabled();
		}			
	}	

	/**
	 * Execute all callbacks associated with a modification of a field
	 *
	 * @param isDifferent True if the content of the boxes is different from the original
	 */
	private void executeModify( boolean isDifferent ) {
		if( !isDifferent )
			modified = false;
		if( modified ) return;
		modified = isDifferent;
		for (Iterator<ProfileEditionCallback> iterator = callbacks.iterator(); iterator.hasNext();) {
			ProfileEditionCallback callback = iterator.next();
			callback.modified(modified);			
		}		

	}

	/**
	 * Execute all callbacks associated with the apply button 
	 *
	 * @param userInfo The information for this user
	 */
	private void executeApply(UserInfo userInfo) {
		
		for (Iterator<ProfileEditionCallback> iterator = callbacks.iterator(); iterator.hasNext();) {
			ProfileEditionCallback callback = iterator.next();
			callback.apply( userInfo );			
		}		

	}

	/**
	 * Execute all callbacks associated with the Cancel button 
	 */
	private void executeCancel() {
		
		for (Iterator<ProfileEditionCallback> iterator = callbacks.iterator(); iterator.hasNext();) {
			ProfileEditionCallback callback = iterator.next();
			callback.cancel();			
		}		

	}

	/**
	 * Indicates if the view should be formatted as a registration page
	 * 
	 * @param registration True to format as a registration page
	 */
	public void setRegistration(boolean registration) {
		
		cancelButton.setVisible( !registration );
		if( registration )
			applyButton.setText( registrationApplyText );
		else
			applyButton.setText( standardApplyText );
	}

	@Override
	public void release() {		
	}

}
